/** * Licensed to the Austrian Association for Software Tool Integration (AASTI) * under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright * ownership. The AASTI licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.openengsb.itests.util; import static org.ops4j.pax.exam.CoreOptions.maven; import static org.ops4j.pax.exam.CoreOptions.mavenBundle; import static org.ops4j.pax.exam.OptionUtils.combine; import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.debugConfiguration; import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.editConfigurationFilePut; import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.karafDistributionConfiguration; import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.logLevel; import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Collection; import java.util.Dictionary; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import javax.inject.Inject; import org.apache.shiro.SecurityUtils; import org.apache.shiro.UnavailableSecurityManagerException; import org.apache.shiro.mgt.SecurityManager; import org.junit.Before; import org.openengsb.connector.usernamepassword.Password; import org.openengsb.core.api.security.AuthenticationContext; import org.openengsb.core.api.security.service.UserDataManager; import org.openengsb.core.workflow.api.RuleManager; import org.openengsb.core.workflow.api.model.RuleBaseElementId; import org.openengsb.core.workflow.api.model.RuleBaseElementType; import org.openengsb.core.workflow.drools.OsgiHelper; import org.openengsb.domain.auditing.AuditingDomain; import org.openengsb.domain.authentication.AuthenticationDomain; import org.openengsb.domain.authentication.AuthenticationException; import org.openengsb.domain.authorization.AuthorizationDomain; import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.ProbeBuilder; import org.ops4j.pax.exam.TestProbeBuilder; import org.ops4j.pax.exam.karaf.options.ConfigurationPointer; import org.ops4j.pax.exam.karaf.options.LogLevelOption; import org.ops4j.pax.exam.karaf.options.configs.ManagementCfg; import org.ops4j.pax.exam.karaf.options.configs.WebCfg; import org.ops4j.pax.exam.options.extra.VMOption; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.Filter; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.util.tracker.ServiceTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableMap; public abstract class AbstractExamTestHelper { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExamTestHelper.class); /* * to configure loglevel and debug-flag, create a file called itests.local.properties in src/test/resources. This * file should only contain simple properties. You can use debug=true and loglevel=INFO in this file. Additional * possible properties are debugport=5005 and hold=true. The debugport option specifies the port where the container * is reachable and the hold option if the container should wait for a debugger to be attached or not. */ private static final int DEBUG_PORT = 5005; private static final String LOG_LEVEL = "ERROR"; public static final long DEFAULT_TIMEOUT = 90000; @Inject private BundleContext bundleContext; private AuthenticationContext authenticationContext; @Before public void setupFramework() throws Exception { waitForFrameworkToStart(); waitForRequiredTasks(); } private void waitForRequiredTasks() throws Exception { authenticationContext = getOsgiService(AuthenticationContext.class); waitForUserDataInitializer(); RuleManager rm = getOsgiService(RuleManager.class); int count = 0; while (rm.getGlobalType("auditing") == null) { LOGGER.warn("waiting for auditing to finish init"); waitasec(); if (count++ > 100) { throw new IllegalStateException("auditing-config did not finish in time"); } } count = 0; while (!rm.listImports().contains(OsgiHelper.class.getName())) { LOGGER.warn("waiting for auditing to finish init"); waitasec(); if (count++ > 100) { throw new IllegalStateException("auditing-config did not finish in time"); } } count = 0; while (rm.get(new RuleBaseElementId(RuleBaseElementType.Process, "humantask")) == null) { LOGGER.warn("waiting for taskboxConfig to finish init"); waitasec(); if (count++ > 100) { throw new IllegalStateException("taskbox-config did not finish in time"); } } authenticationContext = getOsgiService(AuthenticationContext.class); } private void waitForFrameworkToStart() throws Exception { waitForOsgiBundle("org.openengsb.domain.authentication"); waitForOsgiBundle("org.openengsb.domain.authorization"); waitForOsgiBundle("org.openengsb.connector.usernamepassword"); waitForOsgiBundle("org.openengsb.framework.common"); waitForOsgiBundle("org.openengsb.framework.util"); waitForOsgiBundle("org.openengsb.framework.services"); waitForOsgiBundle("org.openengsb.connector.memoryauditing"); queryOsgiService(AuditingDomain.class, null, 10000, true); queryOsgiService(AuthenticationDomain.class, "(location.root=authentication-root)", 25000, true); queryOsgiService(AuthorizationDomain.class, "(location.root=authorization-root)", 25000, true); } private static final Map<Integer, String> STATES = ImmutableMap.of(1, "UNINSTALLED", 2, "INSTALLED", 4, "RESOLVED", 8, "STARTING", 32, "ACTIVE"); private void waitasec() throws InterruptedException { for (Bundle b : bundleContext.getBundles()) { if (b.getState() == Bundle.ACTIVE) { continue; } LOGGER.info(String.format("[%s]-[%s] - %s", b.getBundleId(), STATES.get(b.getState()), b.getSymbolicName())); } Thread.sleep(1000); } protected <T> T getOsgiService(Class<T> type, long timeout) { return getOsgiService(type, null, timeout); } protected <T> T getOsgiService(Class<T> type) { return getOsgiService(type, null, DEFAULT_TIMEOUT); } protected Bundle getInstalledBundle(String symbolicName) { for (Bundle b : bundleContext.getBundles()) { if (b.getSymbolicName().equals(symbolicName)) { return b; } } return null; } protected void waitForSiteToBeAvailable(String urlToWatchFor, Integer maxWaitTime) throws InterruptedException { Integer localCounter = maxWaitTime; while (localCounter != 0) { if (isUrlReachable(urlToWatchFor)) { return; } waitasec(); localCounter--; } throw new IllegalStateException(String.format("Couldn't reach page %s within %s seconds", urlToWatchFor, maxWaitTime)); } @SuppressWarnings("deprecation") protected boolean isUrlReachable(String url) { URL downloadUrl; InputStream is = null; DataInputStream dataInputStream; try { downloadUrl = new URL(url); is = downloadUrl.openStream(); dataInputStream = new DataInputStream(new BufferedInputStream(is)); while (dataInputStream.readLine() != null) { return true; } } catch (Exception e) { // well... what should we say; this could happen... } finally { try { if (is != null) { is.close(); } } catch (IOException ioe) { } } return false; } protected void waitForOsgiBundle(String symbolicName) throws Exception { waitForOsgiBundle(symbolicName, DEFAULT_TIMEOUT); } protected void waitForOsgiBundle(String symbolicName, long timeout) throws Exception { int sleepTime = 1000; int i = 0; Bundle b = null; do { b = getInstalledBundle(symbolicName); if (b.getState() == Bundle.ACTIVE) { break; } // break the loop after timeout to avoid endless loop if ((i * sleepTime) >= timeout) { throw new RuntimeException("bundle " + symbolicName + " didn't start after " + timeout / 1000 + " seconds"); } Thread.sleep(sleepTime); i++; } while (b.getState() != Bundle.ACTIVE); } protected <T> T getOsgiService(Class<T> type, String filter, long timeout) { return queryOsgiService(type, filter, timeout, true); } protected Boolean isOsgiServiceAvailable(Class<?> type, String filter) { return queryOsgiService(type, filter, 100, false) != null; } protected <T> T queryOsgiService(Class<T> type, String filter, long timeout, boolean throwException) { ServiceTracker<T, T> tracker; try { String flt; if (filter != null) { if (filter.startsWith("(")) { flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")" + filter + ")"; } else { flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")(" + filter + "))"; } } else { flt = "(" + Constants.OBJECTCLASS + "=" + type.getName() + ")"; } Filter osgiFilter = FrameworkUtil.createFilter(flt); tracker = new ServiceTracker<T, T>(bundleContext, osgiFilter, null); tracker.open(true); // Note that the tracker is not closed to keep the reference // This is buggy, as the service reference may change i think T svc = tracker.waitForService(timeout); if (svc == null && throwException) { @SuppressWarnings("rawtypes") Dictionary dic = bundleContext.getBundle().getHeaders(); LOGGER.error("Test bundle headers: {}", explode(dic)); for (ServiceReference<?> ref : asCollection(bundleContext.getAllServiceReferences(null, null))) { LOGGER.error("ServiceReference: {}", ref); } for (ServiceReference<?> ref : asCollection(bundleContext.getAllServiceReferences(null, flt))) { LOGGER.error("Filtered ServiceReference: {}", ref); } throw new RuntimeException("Gave up waiting for service " + flt); } return svc; } catch (InvalidSyntaxException e) { throw new IllegalArgumentException("Invalid filter", e); } catch (InterruptedException e) { throw new RuntimeException(e); } } @SuppressWarnings("rawtypes") private static String explode(Dictionary dictionary) { Enumeration keys = dictionary.keys(); StringBuffer result = new StringBuffer(); while (keys.hasMoreElements()) { Object key = keys.nextElement(); result.append(String.format("%s=%s", key, dictionary.get(key))); if (keys.hasMoreElements()) { result.append(", "); } } return result.toString(); } /* * Provides an iterable collection of references, even if the original array is null */ private static Collection<ServiceReference<?>> asCollection(ServiceReference<?>[] references) { List<ServiceReference<?>> result = new LinkedList<ServiceReference<?>>(); if (references != null) { for (ServiceReference<?> reference : references) { result.add(reference); } } return result; } protected BundleContext getBundleContext() { return bundleContext; } protected static String getWorkingDirectory() { return "target/paxrunner/features/"; } protected void authenticateAsAdmin() throws InterruptedException, AuthenticationException { authenticate("admin", "password"); } protected void authenticate(String user, String password) throws InterruptedException, AuthenticationException { authenticationContext.login(user, new Password(password)); } protected void waitForUserDataInitializer() throws InterruptedException { SecurityManager sm = null; int count = 0; while (sm == null) { try { sm = SecurityUtils.getSecurityManager(); } catch (UnavailableSecurityManagerException e) { LOGGER.warn("waiting for security-manager to be set"); waitasec(); } if (count++ > 100) { throw new IllegalStateException("security-manager was not set in time"); } } UserDataManager userDataManager = getOsgiService(UserDataManager.class, "(internal=true)", 20000); count = 0; while (userDataManager.getUserList().isEmpty()) { LOGGER.warn("waiting for users to be initialized"); waitasec(); if (count++ > 100) { throw new IllegalStateException("user-data-initializer did not finish in time"); } } getOsgiService(AuthenticationDomain.class, "(connector=usernamepassword)", 15000); } @ProbeBuilder public TestProbeBuilder probeConfiguration(TestProbeBuilder probe) throws IOException { InputStream stream = ClassLoader.getSystemResourceAsStream("META-INF/maven/dependencies.properties"); Properties depProperties = new Properties(); depProperties.load(stream); String projectVersion = ((String) depProperties .get("org.openengsb.domain/org.openengsb.domain.example/version")) .replace("-", "."); probe.setHeader("Project-Version", projectVersion); return probe; } public static Option[] baseConfiguration() throws Exception { // String loglevel = LOG_LEVEL; String debugPort = Integer.toString(DEBUG_PORT); boolean hold = true; boolean debug = false; InputStream paxLocalStream = ClassLoader.getSystemResourceAsStream("itests.local.properties"); if (paxLocalStream != null) { Properties properties = new Properties(); properties.load(paxLocalStream); // loglevel = (String) ObjectUtils.defaultIfNull(properties.getProperty("loglevel"), loglevel); debugPort = "5005"; // (String) ObjectUtils.defaultIfNull(properties.getProperty("debugport"), debugPort); debug = true; // ObjectUtils.equals(Boolean.TRUE.toString(), properties.getProperty("debug")); hold = true; // ObjectUtils.equals(Boolean.TRUE.toString(), properties.getProperty("hold")); } Properties portNames = new Properties(); InputStream portsPropertiesFile = ClassLoader.getSystemResourceAsStream("ports.properties"); if (portsPropertiesFile == null) { throw new IllegalStateException("ports-configuration not found"); } portNames.load(portsPropertiesFile); LOGGER.warn("running itests with the following port-config"); LOGGER.warn(portNames.toString()); // LogLevel realLogLevel = transformLogLevel(loglevel); Option[] mainOptions = new Option[]{ new VMOption("-Xmx2048m"), new VMOption("-XX:MaxPermSize=256m"), karafDistributionConfiguration().frameworkUrl( maven().groupId("org.openengsb.framework").artifactId("openengsb-framework").type("zip") .versionAsInProject()), logLevel(LogLevelOption.LogLevel.ERROR), editConfigurationFilePut(WebCfg.HTTP_PORT, (String) portNames.get("jetty.http.port")), editConfigurationFilePut(ManagementCfg.RMI_SERVER_PORT, (String) portNames.get("rmi.server.port")), editConfigurationFilePut(ManagementCfg.RMI_REGISTRY_PORT, (String) portNames.get("rmi.registry.port")), editConfigurationFilePut(new ConfigurationPointer("etc/org.openengsb.infrastructure.jms.cfg", "openwire"), (String) portNames.get("jms.openwire.port")), editConfigurationFilePut(new ConfigurationPointer("etc/org.openengsb.infrastructure.jms.cfg", "stomp"), (String) portNames.get("jms.stomp.port")), mavenBundle(maven().groupId("org.openengsb.wrapped").artifactId("net.sourceforge.htmlunit-all") .versionAsInProject())}; mainOptions = combine(mainOptions, getDefaultEDBConfiguration()); if (debug) { return combine(mainOptions, debugConfiguration(debugPort, hold)); } return mainOptions; } private static Option[] getDefaultEDBConfiguration() { String cfg = "etc/org.openengsb.infrastructure.jpa.cfg"; return new Option[]{ editConfigurationFilePut(cfg, "url", "jdbc:h2:mem:itests"), editConfigurationFilePut(cfg, "driverClassName", "org.h2.jdbcx.JdbcDataSource"), editConfigurationFilePut(cfg, "username", ""), editConfigurationFilePut(cfg, "password", "") }; } public String getConfigProperty(String config, String name) throws IOException { ConfigurationAdmin cm = getOsgiService(ConfigurationAdmin.class); Configuration configuration = cm.getConfiguration(config); return (String) configuration.getProperties().get(name); } // private static LogLevel transformLogLevel(String logLevel) { // switch (logLevel) { // case "ERROR": // return LogLevel.ERROR; // case "WARN": // return LogLevel.WARN; // case "INFO": // return LogLevel.INFO; // case "DEBUG": // return LogLevel.DEBUG; // case "TRACE": // return LogLevel.TRACE; // default: // return LogLevel.WARN; // } // } protected String getOsgiProjectVersion() { return bundleContext.getBundle().getHeaders().get("Project-Version"); } }